#include <cassert>
#include <malloc.h>

#include "MemPool.h"

namespace Utils
{
	MemoryPool2::~MemoryPool2()
	{
		MemoryBlock *nextToDelete = mNext;
		while(nextToDelete)
		{
			// Delete the current block.
			mNext = mNext->mNextBlock;
			delete[] reinterpret_cast<uint8_t*> (nextToDelete);
			
			nextToDelete = mNext;
		}
	}
	
	void MemoryPool2::ExpandTheFreeList()
	{
		assert(0 < howMany && "MemoryPool2::ExpandTheFreeList: The number of elements cannot be zero.");
		assert(0 < size && "MemoryPool2::ExpandTheFreeList: The size of elements cannot be zero.");
		
		// Allocate the first element and update the next free element.
		MemoryBlock *block = reinterpret_cast<MemoryBlock*> (new uint8_t [mElementSize]);
		mNext = block;
		
		// Allocate the others and link them.
		const uint32_t allocationsRemaining = mNumberOfExpansions - 1; 
		for (uint32_t i = 0; i < allocationsRemaining; ++i) 
		{
			block->mNextBlock = reinterpret_cast<MemoryBlock*> (new uint8_t [mElementSize]);
			block = block->NextBlock;
		}
	
		// Finish the free list. 
		block->mNextBlock = nullptr;
	}

	MemoryChunk::MemoryChunk(MemoryChunk * const nextChunk, const size_t requestedSize)
		: mChunkSize((requestedSize > DEFAULT_CHUNK_SIZE) ? requestedSize : DEFAULT_CHUNK_SIZE)
		, mMemory(new uint8_t [mChunkSize])
		, mNext(nextChunk)
		, mBytesAlreadyAllocated(0)	
	{
		
	}
	
	ByteMemoryPool::~ByteMemoryPool()
	{
		// Traverse the list of memory chunks and deletes them.
		MemoryChunk *memChunk = listOfMemoryChunks;
		while (memChunk) 
		{
			listOfMemoryChunks = memChunk->nextMemChunk();
			delete memChunk;
			memChunk = listOfMemoryChunks;
		}
	}
	
	void ByteMemoryPool::expandStorage(size_t requestedSize)
	{
		listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks, requestedSize);
	}
	
	// Constructor of this class. It allocate memory block from system and create
	// a static double linked list to manage all memory unit.
	// unitNum: The number of unit which is a part of memory block.
	// unitSize = The size of the unit.
	MemoryPool::MemoryPool(const uint32_t unitNum, const uint32_t unitSize) 
		: mMemBlock(nullptr)
		, mAllocatedMemBlock(nullptr)
		, mFreeMemBlock(nullptr)
		, mBlockSize(unitNum * (unitSize + sizeof(MemoryUnit)))
		, mUnitSize(unitSize)
	{
		//Allocate a memory block.
		mMemBlock = malloc(mBlockSize); 

		assert(mMemBlock && "MemoryPool: NULL pointer");

		// We need to travel the memory block.	
		uint8_t *memBlock = reinterpret_cast<uint8_t *>(mMemBlock); 

		// Compute the unit size like data size + memory unit size.
		const uint32_t finalUnitSize = unitSize + sizeof(MemoryUnit);

		//Link all memory unit.
		for(uint32_t i = 0; i < unitNum; ++i)	
		{
			// Get the memory unit.
			MemoryUnit *currentMemoryUnit = reinterpret_cast<MemoryUnit *>(memBlock);

			//Insert the new unit at head.
			currentMemoryUnit->mPrevious = nullptr;
			currentMemoryUnit->mNext = mFreeMemBlock;		

			// If the current block is not the first, we need to update
			// the previous block.
			if(mFreeMemBlock)
				mFreeMemBlock->mPrevious = currentMemoryUnit;

			mFreeMemBlock = currentMemoryUnit;

			// Point to the next block.
			memBlock += finalUnitSize;
		}
	}

	// Allocate a memory unit. 
	// size: Memory unit size.
	// useMemPool: Whether use memory pool.
	// Return a pointer to a memory unit.
	void* MemoryPool::Alloc(const uint32_t size)
	{
		assert(! (mUnitSize < size || !mMemBlock || !mFreeMemBlock) && "Alloc: There is no free memory");

		//Get a unit from free linked list.
		MemoryUnit *currentUnit = mFreeMemBlock;
		mFreeMemBlock = currentUnit->mNext;			
		if(mFreeMemBlock)
			mFreeMemBlock->mPrevious = nullptr;

		// Insert a new unit at head of allocated linked list.
		currentUnit->mNext = mAllocatedMemBlock;
		if(mAllocatedMemBlock)
			mAllocatedMemBlock->mPrevious = currentUnit; 
		mAllocatedMemBlock = currentUnit;

		// Point to data block of currentUnit and return that address.
		uint8_t *dataBlock = reinterpret_cast<uint8_t *>(currentUnit);
		dataBlock += sizeof(MemoryUnit);

		return reinterpret_cast<void *>(dataBlock);
	}

	// To free a memory unit. Insert it to "Free linked list".
	// p: It point to a memory unit and prepare to free it.
	void MemoryPool::Free(void *p)
	{
		// Get the final address of the memory block.
		uint8_t *memoryBlock = reinterpret_cast<uint8_t *>(mMemBlock);
		memoryBlock += mBlockSize;
		const void * const memoryBlockFinalAddress = reinterpret_cast<const void * const>(memoryBlock);

		// Check that the address of the data to free, is inside our block.
		assert(mMemBlock < p && p < memoryBlockFinalAddress && "Free: There is no free memory");

		// Go back to the address where is the memory unit of this chunk of data.
		memoryBlock = reinterpret_cast<uint8_t *>(p);
		memoryBlock -= sizeof(MemoryUnit);
		MemoryUnit *currentUnit = reinterpret_cast<MemoryUnit *>(memoryBlock);

		// Update the allocated block that will be at head of the linked list.
		mAllocatedMemBlock = currentUnit->mNext;
		if(mAllocatedMemBlock)
			mAllocatedMemBlock->mPrevious = nullptr;

		// Update the free block that will be at head of the linked list.
		currentUnit->mNext = mFreeMemBlock;
		if(mFreeMemBlock)
			mFreeMemBlock->mPrevious = currentUnit;

		mFreeMemBlock = currentUnit;
	}
}
